<!doctype HTML>

<!DOCTYPE html>
<title>Custom Image Upscaling</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript" src="https://unpkg.com/canvaskit-wasm@0.25.0/bin/full/canvaskit.js"></script>

<style>
canvas {
  border: 1px dashed grey;
}
</style>

<body>
  <h1>Custom Image Upscaling</h1>

  <div id=scale_text></div>
  <div class="slidecontainer">
      <input type="range" min="100" max="500" value="100"   class="slider" id="scale_slider">
  </div>

  <canvas id=draw width=1000 height=400></canvas>
</body>

<script type="text/javascript" charset="utf-8">
let CanvasKit;
onload = async () => {
  CanvasKit = await CanvasKitInit({ locateFile: (file) => "https://unpkg.com/canvaskit-wasm@0.25.0/bin/full/" + file });
  init();
};

function init() {
  if (!CanvasKit.RuntimeEffect) {
      console.log(CanvasKit.RuntimeEffect);
      throw "Need RuntimeEffect";
  }
  const surface = CanvasKit.MakeCanvasSurface('draw');
  if (!surface) {
    throw 'Could not make surface';
  }

  const prog = `
  uniform shader image;
  uniform float  sharp;  // slope of the lerp section of the kernel (steeper == sharper)

  float2 sharpen(float2 w) {
      // we think of sharp as a slope on a shifted line
      // y = sharp * (w - 0.5) + 0.5
      // Rewrite with mix needed for some GPUs to be correct
      return saturate(mix(float2(0.5), w, sharp));
  }

  bool nearly_center(float2 p) {
      float tolerance = 1/255.0;
      p = abs(fract(p) - 0.5);
      return p.x < tolerance && p.y < tolerance;
  }

  half4 main(float2 p) {
      // p+1/2, p-1/2 can be numerically unstable when near the center, so we
      // detect that case, and just sample at our center.
      float h = nearly_center(p) ? 0.0 : 0.5;

      // Manual bilerp logic
      half4 pa = image.eval(float2(p.x-h, p.y-h));
      half4 pb = image.eval(float2(p.x+h, p.y-h));
      half4 pc = image.eval(float2(p.x-h, p.y+h));
      half4 pd = image.eval(float2(p.x+h, p.y+h));

      // Now 'sharpen' the weighting. This is the magic sauce where we different
      // from a normal bilerp
      float2 w = sharpen(fract(p + 0.5));
      return mix(mix(pa, pb, w.x),
                 mix(pc, pd, w.x), w.y);
  }
  `;
  const effect = CanvasKit.RuntimeEffect.Make(prog);

  const size = 100;
  const shader_paint = new CanvasKit.Paint();
  const color_paint = new CanvasKit.Paint();

  const image = function() {
    let surf = CanvasKit.MakeSurface(size, size);
    let c = surf.getCanvas();

    color_paint.setColor([1, 1, 1, 1]);
    c.drawRect([0, 0, size, size], color_paint);

    color_paint.setColor([0, 0, 0, 1]);
    for (let x = 0; x < size; x += 2) {
      c.drawRect([x, 0, x+1, size], color_paint);
    }
    return surf.makeImageSnapshot();
  }();

  const imageShader = image.makeShaderOptions(CanvasKit.TileMode.Clamp,
                                              CanvasKit.TileMode.Clamp,
                                              CanvasKit.FilterMode.Nearest,
                                              CanvasKit.MipmapMode.None);

  scale_slider.oninput = () => { surface.requestAnimationFrame(drawFrame); }

  const fract = function(value) {
      return value - Math.floor(value);
  }

  // Uses custom sampling (4 sample points per-pixel)
  draw_one_pass = function(canvas, y, scale) {
      canvas.save();
      canvas.scale(scale, 1.0);
      shader_paint.setShader(effect.makeShaderWithChildren([Math.round(scale)], true, [imageShader], null));
      canvas.drawRect([0, 0, size, y], shader_paint);
      canvas.restore();
  }

  // First creates an upscaled image, and then bilerps it
  draw_two_pass = function(canvas, y, scale) {
      let intScale = Math.max(1, Math.floor(scale + 0.5));
      let intImage = imageAtScale(intScale);

      canvas.save();
      canvas.scale(scale / intScale, 1);
      canvas.drawImageOptions(intImage, 0, y, CanvasKit.FilterMode.Linear, CanvasKit.MipmapMode.None, null);
      canvas.restore();
  }

  drawFrame = function(canvas) {
      const scale = scale_slider.value / 100.0;
      scale_text.innerText = scale

      canvas.clear();

      draw_one_pass(canvas, 100, scale);
      drawMagnified(canvas, 0, 100);

      draw_two_pass(canvas, 200, scale);
      drawMagnified(canvas, 200, 300);
  }

  function drawMagnified(canvas, sampleY, dstY) {
    let pixels = canvas.readPixels(
        0, sampleY,
        { width: 50,
          height: 1,
          colorType: CanvasKit.ColorType.RGBA_8888,
          alphaType: CanvasKit.AlphaType.Premul,
          colorSpace: CanvasKit.ColorSpace.DISPLAY_P3
        }
    );

    for (let i = 0; i < 50; i++) {
      let color =
        [ pixels[i*4 + 0] / 255.0,
          pixels[i*4 + 1] / 255.0,
          pixels[i*4 + 2] / 255.0,
          pixels[i*4 + 3] / 255.0 ];
      color_paint.setColor(color);
      canvas.drawRect([i*20, dstY, (i+1)*20, dstY + 100], color_paint);
    }
  }

  function imageAtScale(s) {
      let surf = CanvasKit.MakeSurface(s * size, size);
      let c = surf.getCanvas();

      color_paint.setColor([1, 1, 1, 1]);
      c.drawRect([0, 0, s * size, size], color_paint);

      color_paint.setColor([0, 0, 0, 1]);
      for (let x = 0; x < size; x += 2) {
        c.drawRect([x * s, 0, (x+1) * s, size], color_paint);
      }
      return surf.makeImageSnapshot();
  }

  surface.requestAnimationFrame(drawFrame);
}

</script>

